﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using StarUtils.Extension;

namespace StarUtils.Cache
{
    public interface IRedisDatabaseProvider
    {
        string DbProviderName { get; }

        IDatabase GetDatabase();

        IEnumerable<IServer> GetServerList();

        ConnectionMultiplexer GetConnectionMultiplexer();
    }
    public class RedisDatabaseProvider : IRedisDatabaseProvider
    {
        private readonly RedisDbOptions _options;

        private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;

        public string DbProviderName { get; }

        public RedisDatabaseProvider(IOptionsMonitor<RedisOptions> options)
        {
            _options = options.CurrentValue.DbConfig;
            _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(new Func<ConnectionMultiplexer>(CreateConnectionMultiplexer));
        }

        public RedisDatabaseProvider(string name, RedisOptions options)
        {
            _options = options.DbConfig;
            _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(new Func<ConnectionMultiplexer>(CreateConnectionMultiplexer));
            DbProviderName = name;
        }

        public IDatabase GetDatabase()
        {
            return _connectionMultiplexer.Value.GetDatabase();
        }

        public IEnumerable<IServer> GetServerList()
        {
            List<EndPoint> mastersServersEndpoints = GetMastersServersEndpoints();
            foreach (EndPoint item in mastersServersEndpoints)
            {
                yield return _connectionMultiplexer.Value.GetServer(item);
            }
        }

        public ConnectionMultiplexer GetConnectionMultiplexer()
        {
            return _connectionMultiplexer.Value;
        }

        private ConnectionMultiplexer CreateConnectionMultiplexer()
        {
            if (_options.Configuration.IsEmpty())
            {
                ConfigurationOptions configurationOptions = new ConfigurationOptions
                {
                    ConnectTimeout = _options.ConnectionTimeout,
                    Password = _options.Password,
                    Ssl = _options.IsSsl,
                    SslHost = _options.SslHost,
                    AllowAdmin = _options.AllowAdmin,
                    DefaultDatabase = _options.Database
                };
                foreach (ServerEndPoint endPoint in _options.EndPoints)
                {
                    configurationOptions.EndPoints.Add(endPoint.Host, endPoint.Port);
                }

                return ConnectionMultiplexer.Connect(configurationOptions);
            }

            return ConnectionMultiplexer.Connect(_options.Configuration);
        }

        private List<EndPoint> GetMastersServersEndpoints()
        {
            List<EndPoint> list = new List<EndPoint>();
            EndPoint[] endPoints = _connectionMultiplexer.Value.GetEndPoints();
            foreach (EndPoint endPoint in endPoints)
            {
                IServer server = _connectionMultiplexer.Value.GetServer(endPoint);
                if (!server.IsConnected)
                {
                    continue;
                }

                if (server.ServerType == ServerType.Cluster)
                {
                    list.AddRange(from p in server.ClusterConfiguration.Nodes
                                  where !p.IsSlave
                                  select p.EndPoint);
                    break;
                }

                if (server.ServerType == ServerType.Standalone && !server.IsSlave)
                {
                    list.Add(endPoint);
                    break;
                }
            }

            return list;
        }
    }
}
